package cn.wgx.commons.security.shiro.filter;

import cn.wgx.commons.security.shiro.exception.SessionKickException;
import cn.wgx.commons.security.shiro.realm.CustomRealm;
import cn.wgx.commons.security.shiro.session.SessionDAO;
import cn.wgx.commons.util.Servlets;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;


/**
 *  并发登录过滤器,控制同一账户最多可登录多少台设备
 */
public class KickoutSessionControlFilter extends AccessControlFilter {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 同一账户,最大同时在线数
     */
    private int maxSession = 1;

    /**
     * 是否踢出最后登录的
     */
    private boolean kickoutAfter = false;

    private String kickoutUrl = "/";

    private Cache<String, Deque<Serializable>> cache;

    private CacheManager cacheManager;

    private String cacheName = "shiro-kickout-session";

    private SessionDAO sessionDAO;

    public Cache<String, Deque<Serializable>> getCache() {
        return cacheManager.getCache(cacheName);
    }

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws SessionKickException, IOException {
        if (!isEnabled()) {
            //如果不需要单用户登录的限制
            return true;
        }

        Subject subject = getSubject(request, response);
        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            //如果没登录，直接进行之后的流程
            return true;
        }
        Session session = subject.getSession();

        Object kick = session.getAttribute(cacheName);
        if(kick != null && (boolean)kick){
            Object ip = session.getAttribute(cacheName+"_ip");
            //sessionDAO.delete(session);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("msg", "此账户已在他处登录.");
            jsonObject.put("success", false);
            if(ip != null){
                jsonObject.put("ip", ip);
            }
            HttpServletResponse httpServletResponse = (HttpServletResponse)response;
            httpServletResponse.setHeader("Content-Type" , "application/json");
            httpServletResponse.setCharacterEncoding("utf-8");
            httpServletResponse.getWriter().write(jsonObject.toJSONString());
            subject.logout();
            return false;
        }

        Serializable currentId = session.getId();
        CustomRealm.Principal principal = (CustomRealm.Principal)subject.getPrincipal();
        String username = principal.getUsername();

//        SecurityUtils.getSecurityManager().getSession(session.getId());
        synchronized (this.cache) {
            if (cache == null) {
                logger.debug("kickFilter cache is null, kickFilter is disabled.");
                return true;
            }
            Deque<Serializable> deque = cache.get(username);
            if (deque == null) {
                deque = new LinkedList<>();
                deque.push(session.getId());
                cache.put(username, deque);
                return true;
            }else{
                boolean whetherPutDeQue = true;
                for (Serializable sessionId : deque) {
                    if (sessionId.equals(currentId)) {
                        whetherPutDeQue = false;
                        break;
                    }
                }
                if (whetherPutDeQue) {
                    deque.push(currentId);
                }
            }

            int n = deque.size();
            //如果队列里的sessionId数超出最大会话数，开始踢人
            while (deque.size() > maxSession) {
                Session kickoutSession = null;
                if (kickoutAfter) { //如果踢出后者
                    kickoutSession = sessionDAO.readSession(deque.pollFirst());
                } else { //否则踢出前者
                    kickoutSession = sessionDAO.readSession(deque.pollLast());
                }
                if (kickoutSession != null) {
                    kickoutSession.setAttribute(cacheName, true);
                    kickoutSession.setAttribute(cacheName+"_ip", Servlets.getRemoteAddr());
                    sessionDAO.update(kickoutSession);
                }
            }
            if(!(n == deque.size())){
                cache.put(username, deque);
            }
        }
        return true;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        /*返回false则进入 onAccessDenied();*/
        return false;
    }

    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
        this.cache = getCache();
    }

    public int getMaxSession() {
        return maxSession;
    }

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public boolean isKickoutAfter() {
        return kickoutAfter;
    }

    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }

    public String getKickoutUrl() {
        return kickoutUrl;
    }

    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }


    public CacheManager getCacheManager() {
        return cacheManager;
    }


    public String getCacheName() {
        return cacheName;
    }

    public void setCacheName(String cacheName) {
        this.cacheName = cacheName;
    }

    public SessionDAO getSessionDAO() {
        return sessionDAO;
    }

    public void setSessionDAO(SessionDAO sessionDAO) {
        this.sessionDAO = sessionDAO;
    }
}
